package com.roamer.audition.server.entity.common;

import com.roamer.audition.common.enums.base.BaseDbEnum;
import com.roamer.util.StringUtils;
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.type.StandardBasicTypes;
import org.hibernate.type.Type;
import org.hibernate.usertype.CompositeUserType;
import org.hibernate.usertype.DynamicParameterizedType;

import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;

/**
 * 数据库枚举类型映射
 * 枚举保存到数据库的是枚举的.getValue()的值，为Integer类型，数据库返回对象时需要把Integer转换枚举
 *
 * @author roamer
 * @version V1.0
 * @date 2019/9/2 1:17 上午
 */
public class DbEnumType implements CompositeUserType, DynamicParameterizedType {

    private Class<?> enumClass;
    private static final Type[] SQL_TYPES = new Type[]{StandardBasicTypes.BYTE};
    private static final String[] PROPERTY_NAME = new String[1];

    @Override
    public void setParameterValues(Properties parameters) {
        final ParameterType reader = (ParameterType) parameters.get(PARAMETER_TYPE);
        if (reader != null) {
            for (Class<?> interfaceClass : reader.getReturnedClass().getInterfaces()) {
                if (interfaceClass.equals(BaseDbEnum.class)) {
                    enumClass = reader.getReturnedClass().asSubclass(Enum.class);
                    break;
                }
            }
        }
        PROPERTY_NAME[0] = (String) parameters.get(PROPERTY);
    }

    @Override
    public String[] getPropertyNames() {
        return PROPERTY_NAME;
    }

    @Override
    public Type[] getPropertyTypes() {
        return SQL_TYPES;
    }

    /**
     * 获取对象中属性的值
     *
     * @param component 枚举
     * @param property  属性的位置
     *
     * @return {@code Object}
     *
     * @throws HibernateException Hibernate异常
     */
    @Override
    public Object getPropertyValue(Object component, int property) throws HibernateException {
        if (component == null) {
            return null;
        }
        return ((BaseDbEnum) component).getValue();
    }

    /**
     * 设置对象中属性的值
     *
     * @param component 枚举
     * @param property  属性的位置
     *
     * @throws HibernateException Hibernate异常
     */
    @Override
    public void setPropertyValue(Object component, int property, Object value) throws HibernateException {
        throw new HibernateException("不可变类型");
    }

    @Override
    public Class returnedClass() {
        return enumClass;
    }

    /**
     * Hibernate执行脏数据检查会对比是否相等，不相等触发update
     *
     * @param x
     * @param y
     *
     * @return {@code true} 相等时
     *
     * @throws HibernateException Hibernate异常
     */
    @Override
    public boolean equals(Object x, Object y) throws HibernateException {
        if (x == null && y == null) {
            return true;
        }
        if (x == null || y == null) {
            return false;
        }
        return x.equals(y);
    }

    /**
     * 获取实例的哈希码，与持久性“相等”一致
     *
     * @param x 值
     *
     * @return {@code int}
     *
     * @throws HibernateException Hibernate异常
     */
    @Override
    public int hashCode(Object x) throws HibernateException {
        return x == null ? 0 : x.hashCode();
    }

    /**
     * Hibernate进行读取结果集时调用
     * 从 ResultSet 读取数据，将其转换为自定义类型后返回
     *
     * @param rs      结果集
     * @param names   自定义类型的映射字段名称
     * @param session Hibernate Session
     * @param owner   对象的所有者
     *
     * @return {@code Object}
     *
     * @throws HibernateException Hibernate异常
     * @throws SQLException       SQL异常
     */
    @Override
    public Object nullSafeGet(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner)
            throws HibernateException, SQLException {
        String value = rs.getString(names[0]);
        if (value == null) {
            return null;
        }
        for (Object object : enumClass.getEnumConstants()) {
            if (((BaseDbEnum) object).equals(Byte.valueOf(value))) {
                return object;
            }
        }
        throw new RuntimeException(
                StringUtils.formatIfArgs("Unknown name value [%s] for enum class [%s]", value, enumClass.getName()));
    }

    /**
     * Hibernate进行数据保存时调用
     * 通过 PreparedStatement 将自定义数据写入到相应的数据库表字段
     *
     * @param st      PreparedStatement
     * @param value   值
     * @param index   字段对应位置
     * @param session Hibernate Session
     *
     * @throws HibernateException Hibernate异常
     * @throws SQLException       SQL异常
     */
    @Override
    public void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor session)
            throws HibernateException, SQLException {
        if (value == null) {
            st.setNull(index, StandardBasicTypes.BYTE.sqlType());
        } else if (value instanceof Byte) {
            st.setByte(index, (Byte) value);
        } else {
            st.setByte(index, ((BaseDbEnum) value).getValue());
        }
    }

    /**
     * 提供自定义类型的拷贝
     * 本方法将用构造返回对象
     * 当nullSafeGet方法调用之后，我们获得了自己定义数据对象。在向用户返回自己定义数据之前，
     * deepCopy方法将被调用。它将根据自己定义数据对象构造一个拷贝。并将此拷贝返回给用户
     * 此时我们就得到了自己定义数据对象的两个版本号，第一个是从数据库读出的原始版本号。其二是我们通过
     * deepCopy方法构造的复制版本号，原始的版本号将有Hibernate维护，复制版由用户使用。原始版本号用作
     * 稍后的脏数据检查根据；Hibernate将在脏数据检查过程中将两个版本号的数据进行对照（通过调用
     * equals方法）。假设数据发生了变化（equals方法返回false），则运行相应的持久化操作
     *
     * @param value 查询出的值
     *
     * @return {@code Object}拷贝对象，返回给用户使用
     *
     * @throws HibernateException Hibernate异常
     */
    @Override
    public Object deepCopy(Object value) throws HibernateException {
        // 不可变类型直接返回即可
        return value;
    }

    /**
     * 本类型实例是否可变
     *
     * @return {@code boolean} 是否可变类型
     */
    @Override
    public boolean isMutable() {
        return false;
    }

    /**
     * 用于自定义类型数据写入二级缓存
     *
     * @param value   查询出的值
     * @param session Hibernate Session
     *
     * @return {@code Serializable}
     *
     * @throws HibernateException Hibernate异常
     */
    @Override
    public Serializable disassemble(Object value, SharedSessionContractImplementor session) throws HibernateException {
        return (Serializable) value;
    }

    /**
     * 从二级缓存中读取自定义类型数据,对于不可变类型，直接返回即可
     *
     * @param cached  缓存值
     * @param session Hibernate Session
     * @param owner   对象的所有者
     *
     * @return {@code Object}
     *
     * @throws HibernateException Hibernate异常
     */
    @Override
    public Object assemble(Serializable cached, SharedSessionContractImplementor session, Object owner)
            throws HibernateException {
        return cached;
    }

    /**
     * 用于更新时替换原本的值,不可变类型直接返回原值即可
     *
     * @param original 原值
     * @param target   新的值
     * @param session  Hibernate Session
     * @param owner    对象的所有者
     *
     * @return {@code Object}
     *
     * @throws HibernateException Hibernate异常
     */
    @Override
    public Object replace(Object original, Object target, SharedSessionContractImplementor session, Object owner)
            throws HibernateException {
        return original;
    }

}
